/*
 * file:        on-disk.c
 * description: skeleton file for CS 7600 homework 4, questions 3 and
 *              4 - the CS7600fs file system.
 *
 * CS 7600, Intensive Computer Systems, Northeastern CCIS
 * Peter Desnoyers, April 2010
 * $Id: my-on-disk.c 158 2010-04-10 15:47:22Z pjd $
 */

#define FUSE_USE_VERSION 27

#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <fuse.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <openssl/blowfish.h>

#include "cs7600fs.h"

/* write_block, read_block:
 *  these are the functions you will use to access the disk
 *  image - *do not* access the disk image file directly.
 *
 *  These functions read or write a single 1024-byte block at a time,
 *  and take a logical block address. (i.e. block 0 refers to bytes
 *  0-1023, block 1 is bytes 1024-2047, etc.)
 *
 *  Make sure you pass a valid block address (i.e. less than the
 *  'fs_size' field in the superblock)
 */
extern int write_block(int lba, void *buf);
extern int read_block(int lba, void *buf);
extern int write_new_key(int lba, unsigned char * key, int keysize);
extern int read_key(int lba, unsigned char * key);
extern int write_block_enc(int lba, void *buf, BF_KEY * cipher);
extern int read_block_enc(int lba, void * buf, BF_KEY * cipher);
extern int is_readable();

struct path {
    int  len;
    char *names[15];
    char buf[256];
};

static struct path *parsepath(const char *s)
{
    struct path *p = calloc(sizeof(*p), 1);
    int i;
    char *t = p->buf;
    strcpy(p->buf, s);
    for (i = 0; (p->names[i] = strtok(t, "/")) != NULL; t = NULL)
        if (++i >= 15)
            break;
    p->len = i;
    return p;
}

int min(int a, int b)
{
    return (a < b) ? a : b;
}
int max(int a, int b)
{
    return (a > b) ? a : b;
}

char super_block[BLOCK_SIZE];
struct cs7600fs_super *super;
struct cs7600fs_entry *fat;
int 						 *free_list;
int  						 free_size, free_i;
char                  *fat_buf;
int                   *fat_modified;
BF_KEY		 	 		 current_enc;
int						 current_key;
BF_KEY					 *keylist;

#define DES_PER_BLK BLOCK_SIZE / sizeof(struct cs7600fs_dirent)
#define FATS_PER_BLK BLOCK_SIZE / sizeof(struct cs7600fs_entry)

/* lookup - looks up a directory entry and returns:
 *    <return value> -  error code (less than 0)
 *                      disk block number (greater than 0)
 *    buf[]       - disk block that directory resides in
 *    *de_retval  - pointer to dirent within disk block
 *    *blk_retval - block number to write back disk block
 *
 *    NOTE - 'buf' must point to BLOCK_SIZE bytes of memory.
 */

#define WRITE_DIR 1
int lookup(char *names[], int nnames, char *buf,
	   struct cs7600fs_dirent **de_retval, int write_dir)
{
    int i, j, blk = 0, err = 0;
    struct cs7600fs_dirent *de = &super->root_dirent;

    for (i = 0; i < nnames && err == 0; i++) {
	if (!de->isDir) 
	    err = -ENOTDIR;
	else {
	    blk = de->start;
	    read_block(blk, buf);
	    de = (struct cs7600fs_dirent *)buf;
	
	    for (j = 0; j < DES_PER_BLK; j++, de++)
		if (de->valid && strcmp(de->name, names[i]) == 0)
		    break;

	    if (j == DES_PER_BLK)
		err = -ENOENT;
	    else if (i < nnames-1 && !de->isDir)
		err = -ENOTDIR;
	    else if (i < nnames-1 && !(de->mode & S_IXUSR))
		err = -EACCES;
	    else if (i == nnames-2 && write_dir && !(de->mode & S_IWUSR))
		err = -EACCES;
	}
    }

    if (err == 0 && de_retval != NULL)
	*de_retval = de;
    if (err == 0)
	err = blk;

    return err;
}

void update_fat(int blk, int inUse, int next, int eof)
{
    fat_modified[blk / FATS_PER_BLK] = 1;
    fat[blk].inUse = inUse;
    fat[blk].next = next;
    fat[blk].eof = eof;
}
enum {in_use = 1, not_in_use = 0, no_next = 0, not_eof = 0, is_eof = 1};

/* flush only those blocks in the FAT that have been modified
 */
static void flush_fat(void)
{
    int i;
    for (i = 0; i < super->fat_len; i++)
	if (fat_modified[i] || 1) {
	    fat_modified[i] = 0;
	    write_block(i+1, (char*)fat + i*BLOCK_SIZE);
	}
}

/* Find a free block in the File Allocation Table.
 * Returns <0 for error.
 */
static int alloc_block(void)
{
    /*int i;
    for (i = 0; i < super->fs_size; i++)
	if (!fat[i].inUse) {
	    update_fat(i, in_use, no_next, is_eof);
	    return i;
	}*/
	if( free_i >= free_size )
    return -ENOSPC;
   else 
   {
   	update_fat(free_list[free_i], in_use, no_next, is_eof);
   	free_i++;
   	return free_list[free_i - 1];
   }
}

/* init - called once at startup
 * This might be a good place to read in the super-block and set up
 * any global variables you need. You don't need to worry about the
 * argument or the return value.
 */
void* ondisk_init(struct fuse_conn_info *conn)
{
	srand( time(NULL) );	
	
    read_block(0, super_block);
    super = (struct cs7600fs_super*)super_block;

    /* we keep an in-memory copy of the FAT, and track which blocks in
     * it have been modified.
     */
    fat_buf = calloc(BLOCK_SIZE * super->fat_len, 1);
    int i;
    for (i = 0; i < super->fat_len; i++)
	read_block(i+1, &fat_buf[i*BLOCK_SIZE]);
    fat = (struct cs7600fs_entry*)fat_buf;
    fat_modified = calloc(super->fat_len * sizeof(int), 1);
    
    free_list = calloc(sizeof(int), super->fs_size);
    
    i = 0;
    int x;
    for( x = 0; x < super -> fs_size; x++ )
    {
    	if( !fat[x].inUse && x != super -> key_start )
    	{
    		free_list[i] = x;
    		i++;
		}
	 }
	 
	 free_size = i;
	 free_i = 0;
	 
	 //Fisher-Yates shuffle
	 int t;
	 for( x = free_size - 1; x > 0; x-- )
	 {
	 	i = rand() % x;
	 	t = free_list[i];
	 	free_list[i] = free_list[x];
	 	free_list[x] = t;
	 }
	 
	 i = super->key_start;
	 int keyblk = i;
	 current_key = 0;
    if( !fat[i].inUse ) //First mount
    {
    	update_fat(i, in_use, no_next, is_eof);
    }
    else {
    	 current_key++;
		 while(fat[i].eof != is_eof && fat[i].next != 0)
		 {
		 	printf("Getting there: %d\n", i);
		 	i = fat[i].next;
		 	current_key++;
		 }
		 	
		 keyblk = alloc_block();
		 if( keyblk == -ENOSPC )
		 {
		 	printf("Ran out of space\n");
		 	exit(-1);
		 }
		 update_fat(i, in_use, keyblk, not_eof);
		 printf("Changing %d to point to %d\n", i, keyblk);
	}
	
	flush_fat();
	
	printf("Block: %d\n", keyblk);
	
	unsigned char * key = calloc(16, sizeof(char));
	write_new_key(keyblk, key, 16);
	BF_set_key( &current_enc, 16, key );
	
	printf("Current key: %d\n", current_key);
	
	if( is_readable() )
	{
		keylist = calloc(current_key+1, sizeof(BF_KEY));
		for( i = 0, x = super->key_start; x != 0 && i <= current_key; x = fat[x].next, i++ )
		{
			read_key(x, key);
			BF_set_key( &keylist[i], 16, key );
		}
	}
    
    return NULL;
}

/* mark a block as free in the FAT
 */
static void free_block(int blk)
{
    update_fat(blk, not_in_use, no_next, not_eof);
}

/* Note on path translation errors:
 * In addition to the method-specific errors listed below, almost
 * every method can return one of the following errors if it fails to
 * locate a file or directory corresponding to a specified path.
 *
 * ENOENT - a component of the path is not present.
 * ENOTDIR - an intermediate component of the path (e.g. 'b' in
 *           /a/b/c) is not a directory
 * EACCES  - the user lacks *execute* permission (yes, execute) for an
 *           intermediate directory in the path.
 *
 * In our case we assume a single user, and so it is sufficient to
 * use this test:    if ((mode & S_IXUSR) == 0)
 *                        return -EACCES;
 *
 * See 'man path_resolution' for more information.
 */

void hw2stat(struct stat *sb, struct cs7600fs_dirent *de)
{
    memset(sb, 0, sizeof(*sb));
    sb->st_mode = de->mode | (de->isDir ? S_IFDIR : S_IFREG);
    sb->st_nlink = 1;
    sb->st_uid = de->uid;
    sb->st_gid = de->gid;
    sb->st_size = de->length;
    sb->st_blocks = (de->length + BLOCK_SIZE - 1) / BLOCK_SIZE;
    sb->st_atime = sb->st_mtime = sb->st_ctime = time( NULL );//de->mtime;
}

/* getattr - get file or directory attributes. For a description of
 *  the fields in 'struct stat', see 'man lstat'.
 * Note - fields not provided in CS7600fs are:
 *    st_nlink - always set to 1
 *    st_atime, st_ctime - set to same value as st_mtime
 * errors - path translation, ENOENT
 */
static int ondisk_getattr(const char *path, struct stat *sb)
{
    struct path *p = parsepath(path);
    struct cs7600fs_dirent *de;
    char buf[BLOCK_SIZE];
    
    int err = lookup(p->names, p->len, buf, &de, 0);
    if (err >= 0) {
	hw2stat(sb, de);
	err = 0;
    }

    free(p);
    return err;
}

/* readdir - get directory contents
 * for each entry in the directory, invoke:
 *     filler(buf, <name>, <statbuf>, 0)
 * where <statbuf> is a struct stat, just like in getattr.
 * Errors - path resolution, EACCES, ENOTDIR, ENOENT
 *
 * EACCES is returned if the user lacks *read* permission to the
 * directory - i.e.:     if ((mode & S_IRUSR) == 0)
 *                           return -EACCES;
 */
static int ondisk_readdir(const char *path, void *ptr, fuse_fill_dir_t filler,
		       off_t offset, struct fuse_file_info *fi)
{
    int err, i;
    char buf[BLOCK_SIZE];
    struct cs7600fs_dirent *de;
    struct stat sb;
    struct path *p = parsepath(path);

    if ((err = lookup(p->names, p->len, buf, &de, 0)) >= 0) {
	if (!de->isDir)
	    err = -ENOTDIR;
	else {
	    read_block(de->start, buf);
	    for (i = 0, de = (void*)buf; i < DES_PER_BLK; i++, de++)
		if (de->valid) {
		    hw2stat(&sb, de);
		    filler(ptr, de->name, &sb, 0);
		}
	    err = 0;
	}
    }

    free(p);
    return err;
}

// add an entry in the indicated directory, allocating a new disk
// block if necessary. Returns a pointer to the directory entry
// (*de_ptr) and the block number; block contents are stored in 'buf'
//
static int add_direntry(char **names, int nnames,
			char *buf, struct cs7600fs_dirent **de_ptr)
{
    struct cs7600fs_dirent *de = NULL;
    int i, blk = lookup(names, nnames, buf, &de, 0);
    int x = 0;
    int * free_dirents = calloc(DES_PER_BLK, sizeof(int));
     
    if (blk < 0)
	return blk;
    else if (!de->isDir)
	return -ENOTDIR;

    blk = de->start;
    read_block(blk, buf);
    de = (struct cs7600fs_dirent*)buf;

    for (i = 0; i < DES_PER_BLK; i++)
    {
		if (!de[i].valid)
		 {   
		 	free_dirents[x] = i;
		   x++;
		  }
	}
    if (x == 0)
	return -ENOSPC;
	
	

    *de_ptr = &de[free_dirents[rand() % x]];
    free(free_dirents);
    return blk;
}

static int do_create(char **names, int nnames, int mode, int isdir, char *buf,
		     struct cs7600fs_dirent **de_ptr)
{
    int val = 0, blknum;
    struct cs7600fs_dirent *de = NULL;
    struct fuse_context *fc = fuse_get_context();

    if (lookup(names, nnames, buf, NULL, 0) >= 0)
	val = -EEXIST;
    if (val == 0)
	val = lookup(names, nnames-1, buf, &de, 0);
    if (val < 0)
	/* pass */;
    else if (!de->isDir)
	val = -ENOTDIR;
    else if ((de->mode & S_IWUSR) == 0)
	val = -EACCES;
    else {
	blknum = add_direntry(names, nnames-1, buf, &de);
	de->valid = 1;
	de->isDir = isdir;
	de->uid = fc->uid;
	de->gid = fc->gid;
	de->mode = mode;
	de->mtime = 0;//time(NULL);
	de->start = de->length = 0;
	strcpy(de->name, names[nnames-1]);

	val = blknum;
	if (de_ptr != NULL)
	    *de_ptr = de;
    }
    
    return val;
}

/* create - create a new file
 *   object permissions:    (mode & 01777)
 * Errors - path resolution, EACCES, EEXIST
 *
 * EACCES is returned if the user does not have write permission to
 * the enclosing directory:
 *       if ((mode & S_IWUSR) == 0)
 *           return -EACCES;
 * If a file or directory of this name already exists, return -EEXIST.
 */
int ondisk_create(const char *path, mode_t mode,
		  struct fuse_file_info *fi)
{
    char buf[BLOCK_SIZE];
    int blknum, val = 0;
    struct path *p = parsepath(path);

    if ((blknum = do_create(p->names, p->len, mode, 0, buf, NULL)) < 0)
	val = blknum;
    else {
	write_block(blknum, buf);
	flush_fat();
    }

    free(p);
    return val;
}

/* mkdir - create a directory with the given mode.
 * Errors - path resolution, EACCES, EEXIST
 * Conditions for EACCES and EEXIST are the same as for create.
 */ 
static int ondisk_mkdir(const char *path, mode_t mode)
{
    int val = 0, leaf, parent;
    struct path *p = parsepath(path);
    struct cs7600fs_dirent *de;
    char buf[BLOCK_SIZE];

    if ((leaf = alloc_block()) < 0)
	val = -ENOSPC;
    else {
	/* we can safely write empty directory entries to the newly
	 * allocated block, before we know whether the remainder of
	 * the operation will succeed or fail.
	 */
	memset(buf, 0, sizeof(buf));
	write_block(leaf, buf);
	
	if ((parent = do_create(p->names, p->len, mode, 1, buf, &de)) < 0) {
	    free_block(leaf);
	    val = parent;
	}
	else {
	    de->start = leaf;
	    write_block(parent, buf);
	    flush_fat();
	}
    }

    free(p);
    return val;
}

/* unlink - delete a file
 *  Errors - path resolution, EACCES, ENOENT, EISDIR
 * Requires user *write* permission to the containing
 *  directory. (permissions on the file itself are ignored)
 */
static int ondisk_unlink(const char *path)
{
    int blknum, val = 0, i, next;
    struct cs7600fs_dirent *de;
    char buf[BLOCK_SIZE];
    struct path *p = parsepath(path);

    if ((blknum = lookup(p->names, p->len, buf, &de, WRITE_DIR)) < 0)
	val = blknum;
    else if (de->isDir)
	val = -EISDIR;
    else {
	for (i = de->start; i > 0; i = next) {
	    next = (fat[i].eof ? -1 : fat[i].next);
	    free_block(i);
	}
	de->valid = 0;
	write_block(blknum, buf);
	flush_fat();
    }

    free(p);
    return val;
}

/* rmdir - remove a directory
 *  Errors - path resolution, EACCES, ENOENT, ENOTDIR, ENOTEMPTY
 * Requires user *write* permission to the containing
 *  directory. (permissions on the directory itself are ignored)
 */
static int ondisk_rmdir(const char *path)
{
    int blknum, val = 0, i;
    struct cs7600fs_dirent *de;
    char buf[BLOCK_SIZE];
    struct path *p = parsepath(path);

    if ((blknum = lookup(p->names, p->len, buf, &de, WRITE_DIR)) < 0)
	val = blknum;
    else if (!de->isDir)
	val = -ENOTDIR;
    else {
	/* first read the directory and make sure it's empty.
	 */
	char buf2[BLOCK_SIZE];
	read_block(de->start, buf2);
	struct cs7600fs_dirent *de2 = (struct cs7600fs_dirent *)buf2;
	for (i = 0; i < DES_PER_BLK; i++)
	    if (de2[i].valid)
		val = -ENOTEMPTY;

	/* if we can remove it, first free the block, then release the
	 * parent directory entry, then flush to disk.
	 */
	if (val == 0) {
	    free_block(de->start);
	    write_block(blknum, buf);
	    flush_fat();
	}
    }

    free(p);
    return val;
}

/* rename - rename a file or directory
 * Errors - path resolution, ENOENT, EACCES, EINVAL, EEXIST
 *
 * ENOENT - source does not exist
 * EEXIST - destination already exists
 * EACCES - no write permission to directory. Permissions 
 * EINVAL - source and destination are not in the same directory
 *
 * Note that this is a simplified version of the UNIX rename
 * functionality - see 'man 2 rename' for full semantics. In
 * particular, the full version can move across directories, replace a
 * destination file, and replace an empty directory with a full one.
 */
static int ondisk_rename(const char *src_path, const char *dst_path)
{
    struct path *sp = parsepath(src_path), *dp = parsepath(dst_path);
    int i, val = 0, blk;
    char buf[BLOCK_SIZE];
    struct cs7600fs_dirent *de;
    
    /* For now we chicken out and only rename within the same
     * directory. 
     */
    if (sp->len != dp->len)
	val = -EOPNOTSUPP;
    for (i = 0; val == 0 && i < sp->len-1; i++)
	if (strcmp(sp->names[i], dp->names[i]) != 0)
	    val = -EOPNOTSUPP;

    /* We're going to cheat a bit and refuse to overwrite a
     * destination file.
     */
    if (val == 0 && lookup(dp->names, dp->len, buf, &de, 0) >= 0)
	val = -EEXIST;

    /* Finally look up the source dirent and change its name.
     */
    if (val == 0) {
	blk = lookup(sp->names, sp->len, buf, &de, 0);
	if (blk < 0)
	    val = blk;
	else {
	    strcpy(de->name, dp->names[dp->len-1]);
	    write_block(blk, buf);
	    val = 0;
	}
    }

    free(sp);
    free(dp);
    return val;
}

/* Modify a directory entry. The infrastructure is the same for
 * chmod and utimes, so we factor it out into this common function
 * and then make the actual modification from a function pointer.
 */
static int modify_dirent(const char *path,
			 void (*f)(struct cs7600fs_dirent*,void*), void *ctx)
{
    int blknum, val = 0;
    struct path *p = parsepath(path);
    struct cs7600fs_dirent *de;
    char buf[BLOCK_SIZE];

    if ((blknum = lookup(p->names, p->len, buf, &de, 0)) < 0)
	val = blknum;
    else {
	f(de, ctx);
	write_block(blknum, buf);
    }

    free(p);
    return val;
}

/* chmod - change file permissions
 * utime - change access and modification times
 *         (for definition of 'struct utimebuf', see 'man utime')
 *
 * Errors - path resolution, ENOENT.
 *
 * Note that no write permissions to the file/directory or containing
 * are needed - if you can resolve the path, then you can make the
 * change. (normally EACCES is returned if the invoking user does not
 * own the file) 
 */
static void do_chmod(struct cs7600fs_dirent *de, void *ptr)
{
    mode_t *mode = ptr;
    de->mode = *mode;
}
static int ondisk_chmod(const char *path, mode_t mode)
{
    return modify_dirent(path, do_chmod, &mode);
}

/* utime - change access and modification times
 * for definition of 'struct utimebuf', see 'man utime'
 * Errors - path resolution, EACCES, ENOENT
 */
static void do_utime(struct cs7600fs_dirent *de, void *ptr)
{
    struct utimbuf *ut = ptr;
    //de->mtime = ut->modtime;
}
int ondisk_utime(const char *path, struct utimbuf *ut)
{
    return modify_dirent(path, do_utime, ut);
}

/* truncate - truncate file to exactly 'len' bytes
 * Errors - path resolution, EACCES, ENOENT, EISDIR
 * EACCES - requires write permission to the file itself.
 */
static int ondisk_truncate(const char *path, off_t len)
{
    int blknum, val = 0;
    struct cs7600fs_dirent *de;
    char buf[BLOCK_SIZE];

    struct path *p = parsepath(path);
    if ((blknum = lookup(p->names, p->len, buf, &de, 0)) < 0)
	val = blknum;
    else if (de->isDir)
	val = -EISDIR;
    else if ((de->mode & S_IWUSR) == 0)
	val = -EACCES;
    else if (len < de->length) {
	int i, this, next = 0;

	for (i = 0, this = de->start; next != -1; i++, this = next) {
	    next = fat[this].eof ? -1 : fat[this].next;
	    if (i == len / BLOCK_SIZE)
		update_fat(this, in_use, no_next, is_eof);
	    if (i > len / BLOCK_SIZE)
		free_block(this);
	}

	de->length = len;
	if (len == 0)
	    de->start = 0;

	flush_fat();
	write_block(blknum, buf);
    }

    free(p);
    return val;
}

/* read - read data from an open file.
 * should return exactly the number of bytes requested, except:
 *   - if offset >= len, return 0
 *   - on error, return <0
 * Errors - path resolution, ENOENT, EISDIR, EACCES
 * EACCES - requires read permission to the file itself
 */
static int ondisk_read(const char *path, char *data, size_t len,
		       off_t offset, struct fuse_file_info *fi)
{
    struct path *p = parsepath(path);
    struct cs7600fs_dirent *de;
    char buf[BLOCK_SIZE];
    int err, copied = 0;

    err = lookup(p->names, p->len, buf, &de, 0);
    if (err < 0)
	/* pass */;
    else if (de->isDir)
	err = -EISDIR;
    else if (!(de->mode & S_IRUSR))
	err = -EACCES;
    else if (offset >= de->length)
	err = 0;
    else {
	len = min(offset+len,de->length) - offset;
	int blk = de->start;
	
	while (offset >= BLOCK_SIZE) {
	    if (fat[blk].eof)
		blk = 0;
	    else
		blk = fat[blk].next;
	    offset -= BLOCK_SIZE;
	}

	while (len > 0 && blk != 0) {
	    int tocopy = min(BLOCK_SIZE-offset, len);
	    if( is_readable() )
	    {
	    	read_block_enc(blk, buf, &keylist[fat[blk].keyIndex]);
	    }
	    else 
	    {
	    	read_block(blk, buf);
	    }
	    memcpy(data, &buf[offset], tocopy);

	    offset = 0;
	    data += tocopy;
	    len -= tocopy;
	    copied += tocopy;
	    blk = fat[blk].eof ? 0 : fat[blk].next;
	}
	err = copied;
    }

    free(p);
    return err;
}

static void validate_de(struct cs7600fs_dirent *de)
{
    if (de == NULL || de->isDir)
	return;
    int blk = de->start, nblks = (de->length + BLOCK_SIZE - 1) / BLOCK_SIZE;
    while (blk != 0) {
	nblks--;
	if (fat[blk].eof)
	    break;
	blk = fat[blk].next;
    }
    assert(nblks==0);
}
    
/* write - write data to a file
 * It should return exactly the number of bytes requested, except on
 * error.
 * Errors - path resolution, ENOENT, EISDIR, EACCES
 * EACCES - requires write permission to the file itself
 */
static int ondisk_write(const char *path, const char *data, size_t len,
		     off_t offset, struct fuse_file_info *fi)
{
    struct path *p = parsepath(path);
    int err = 0, copied = 0, current = offset, dir_blk;
    struct cs7600fs_dirent *de;

    /* We use one buffer for the data blocks, and another to keep the
     * directory around so that we can update the length and mtime
     * afterwards. 
     */
    char dbuf[BLOCK_SIZE], fbuf[BLOCK_SIZE];

    if (len == 1)
	printf("len=1\n");
    
    dir_blk = lookup(p->names, p->len, dbuf, &de, 0);
    validate_de(de);
    
    if (dir_blk <= 0)
	err = dir_blk;
    else if (de->isDir)
	err = -EISDIR;
    else if (offset > de->length)
	err = -EINVAL;
    else if (!(de->mode & S_IWUSR))
	err = -EACCES;
    else if (len == 0)
	err = 0;
    else {
	int blk = de->start, prev = 0;
	memset(fbuf, 0, sizeof(fbuf));

	/* Now we skip over the offset.
	 */
	while (offset >= BLOCK_SIZE && blk != 0) {
	    prev = blk;
	    blk = fat[blk].eof ? 0 : fat[blk].next;
	    offset -= BLOCK_SIZE;
	}
	assert(offset < BLOCK_SIZE);
	
	/* Now we write the data. 
	 */
	while (len > 0) {
	    int tocopy = min(BLOCK_SIZE-offset, len);

	    if (blk == 0) {
		if ((blk = alloc_block()) < 0) {
		    err = blk;
		    goto done;	/* goto used as a pseudo-exception */
		}
		if (prev == 0)
		    de->start = blk;
		else
		    update_fat(prev, in_use, blk, not_eof);
	    }
	    
	    /* If we're overwriting, we may have to do a
	     * read-modify-write for the first and/or last block
	     */
	    if (offset > 0 || (tocopy < BLOCK_SIZE &&
			       current+tocopy < de->length)) 
		read_block(blk, fbuf);

	    memcpy(&fbuf[offset], data, tocopy);
	    write_block_enc(blk, fbuf, &current_enc);
	    fat[blk].keyIndex = current_key;

	    data += tocopy;
	    len -= tocopy;
	    copied += tocopy;
	    current += tocopy;
	    offset = 0;

	    prev = blk;
	    if (fat[blk].eof)
		blk = 0;
	    else
		blk = fat[blk].next;
	}

	err = copied;
	if (current > de->length)
	    de->length = current;
	de->mtime = 0; //time(NULL);
	write_block(dir_blk, dbuf);
	flush_fat();
    }

done:
    free(p);
    return err;
}

/* statfs - get file system statistics
 * see 'man 2 statfs' for description of 'struct statvfs'.
 * Errors - none. Needs to work.
 */
static int ondisk_statfs(const char *path, struct statvfs *st)
{
    int i, n;
    for (i = n = 0; i < super->fs_size; i++)
	if (!fat[i].inUse)
	    n++;
    st->f_bsize = BLOCK_SIZE;
    st->f_blocks = super->fs_size - 1 - super->fat_len;
    st->f_bfree = st->f_bavail = st->f_files =
	st->f_ffree = n;
    st->f_namemax = 43;

    return 0;
}

/* operations vector. Please don't rename it, as the skeleton code in
 * misc.c assumes it is named 'hw4_ops'.
 */
struct fuse_operations hw4_ops = {
    .init = ondisk_init,
    .getattr = ondisk_getattr,
    .readdir = ondisk_readdir,
    .create = ondisk_create,
    .mkdir = ondisk_mkdir,
    .unlink = ondisk_unlink,
    .rmdir = ondisk_rmdir,
    .rename = ondisk_rename,
    .chmod = ondisk_chmod,
    .utime = ondisk_utime,
    .truncate = ondisk_truncate,
    .read = ondisk_read,
    .write = ondisk_write,
    .statfs = ondisk_statfs,
};

int need_img = 1;
